﻿// -------------------------------------------------------------------------------------------------------
// LICENSE INFORMATION
//
// - This software is licensed under the MIT shared source license.
// - The "official" source code for this project is maintained at http://mtcagent.codeplex.com
//
// Copyright (c) 2010 OpenNETCF Consulting
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial 
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
// -------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.IO;
using System.Reflection;
using OpenNETCF.IoC;
using System.Diagnostics;
using System.Xml.Linq;
using System.Threading;
using System.Net.Sockets;
using System.Net;

namespace OpenNETCF.MTConnect
{
    internal class SHDRValue
    {
        public SHDRValue(string id, string value)
        {
            ID = id;
            Value = value;
        }

        public string ID { get; set; }
        public string Value { get; set; }
    }

    internal class SHDRListener : DisposableBase
    {
        public event EventHandler<GenericEventArgs<SHDRValue>> DataItemSet;
        public event EventHandler<GenericEventArgs<SHDRValue>> ConditionSet;

        private TcpClient m_client;
        private int m_bufferSize = 8096;
        private AutoResetEvent m_dataReady = new AutoResetEvent(false);
        private List<string> m_conditions = new List<string>();
        private List<string> m_nonconditions = new List<string>();
        private Queue<string> m_input = new Queue<string>();
        private bool m_keepRunning;
        private bool m_running = false;

        private int Port { get; set; }

        public SHDRListener(int port)
        {
            Port = port;
        }

        protected override void ReleaseManagedResources()
        {
            Stop();
        }

        private void ListenerThreadProc()
        {
            while (m_client == null)
            {
                try
                {
                    m_client = new TcpClient("localhost", Port);
                }
                catch (SocketException)
                {
                    Thread.Sleep(5000);
                }
            }

            while (m_keepRunning)
            {
                var buffer = new byte[m_bufferSize];

                if (m_client.Client.Connected)
                {
                    var read = m_client.GetStream().Read(buffer, 0, buffer.Length);
                    var str = Encoding.ASCII.GetString(buffer, 0, read);
                    if (str[str.Length-1] != '\n')
                    {
                    }
                    var items = str.Split('\n');
                    foreach (var item in items.Where(d => !d.IsNullOrEmpty()))
                    {
                        m_input.Enqueue(item);
                    }
                    m_dataReady.Set();
                }

                Thread.Sleep(1000);
            }
        }

        private void ParserThreadProc()
        {
            string id;
            string value;

            while (m_keepRunning)
            {
                m_dataReady.WaitOne(1000);

                while (m_input.Count > 0)
                {
                    var data = m_input.Dequeue();

                    var items = data.Split('|');

                    var itemTime = DateTime.Parse(items[0]);
                    id = items[1];
                    value = items[2];

                    if (m_nonconditions.Contains(id))
                    {
                        ParseNonCondition(itemTime, items);
                    }
                    else if (m_conditions.Contains(id))
                    {
                        ParseCondition(itemTime, items);
                    }
                    else if (id.Contains(':'))
                    {
                        // this is an ASSET item - not currently supported
                        Debug.WriteLine("Ignoring unsupported ASSET command: " + data);
                    }
                    else if (value == "NORMAL" || value == "WARNING" || value == "FAULT" /* || value == "UNAVAILABLE" */ )
                    {
                        m_conditions.Add(id);
                        ParseCondition(itemTime, items);
                    }
                    else
                    {
                        m_nonconditions.Add(id);
                        ParseNonCondition(itemTime, items);
                    }
                }                
            }
        }

        private void ParseNonCondition(DateTime itemTime, string[] items)
        {
            string id;
            string value;
            var i = 1;

            while (i < items.Length)
            {
                id = items[i];
                value = items[i + 1];

                DataItemSet.Fire(this, new GenericEventArgs<SHDRValue>(new SHDRValue(id, value)));
 
//                Debug.WriteLine(string.Format("@{0}: {1}={2}", itemTime.ToShortTimeString(), id, value));
                i += 2;
            }
        }

        private void ParseCondition(DateTime itemTime, string[] items)
        {
            var id = items[1];
            var condition = items[2];
            var reserved1 = items[3];
            var reserved2 = items[4];
            var reserved3 = items[5];
            var description = items[6];

            // TODO: enable passing "text" (i.e. CDATA) associated with condition
            ConditionSet.Fire(this, new GenericEventArgs<SHDRValue>(new SHDRValue(id, condition)));

            //Debug.WriteLine(string.Format("@{0}: CONDITION {1}={2} {3}",
            //    itemTime.ToShortTimeString(),
            //    id,
            //    condition,
            //    description.IsNullOrEmpty() ? string.Empty : string.Format("({0})", description)));
        }

        public void Start()
        {
            lock (this)
            {
                if (m_running) return;

                m_keepRunning = true;

                new Thread(ListenerThreadProc)
                {
                    IsBackground = true,
                    Name = "SHDR Listener"
                }
                .Start();

                new Thread(ParserThreadProc)
                {
                    IsBackground = true,
                    Name = "SHDR Listener"
                }
                .Start();

                m_running = true;
            }
        }

        public void Stop()
        {
            lock (this)
            {
                m_keepRunning = false;
                m_running = false;
            }
        }
    }
}
